package org.common.dispatcher;

import com.google.protobuf.GeneratedMessageV3;
import lombok.extern.slf4j.Slf4j;
import org.common.handler.BaseProtoHandler;
import org.common.netty.Pack;
import org.common.netty.PackCodec;
import org.common.utils.SpringUtils;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * 协议逻辑分发器
 */
@Slf4j
@Component
public class ProtoDispatcher {

    private final Map<Integer, ProtoWorker> workerMap = new HashMap<>();

    /**
     * 载入分发数据
     */
    public void load(Set<Class<?>> classes) throws NoSuchMethodException {
        for (Class<?> clz : classes) {
            if (clz.getSuperclass() != BaseProtoHandler.class) {
                continue;
            }
            Object protoHandler = SpringUtils.getBean(clz);
            Method[] methods = clz.getDeclaredMethods();
            for (Method method : methods) {
                CMD annotation = method.getAnnotation(CMD.class);
                if (annotation == null) {
                    continue;
                }
                int cmdId = annotation.value();
                if (workerMap.containsKey(cmdId)) {
                    // 出现重复cmdId
                    String err = "cmdId " + cmdId + " is duplicate.";
                    throw new RuntimeException(err);
                }
                workerMap.put(cmdId, new ProtoWorker(cmdId, protoHandler, method));
            }
        }
    }

    /**
     * 分发协议
     * @param cmdId 协议号
     * @param data  协议内容
     * @param obj   玩家数据
     * @return 要返回给客户端的Pack
     */
    public Pack dispatch(int cmdId, byte[] data, Object obj) throws InvocationTargetException, IllegalAccessException {
        ProtoWorker protoWorker = workerMap.get(cmdId);
        if (protoWorker == null) {
            log.warn("not find proto worker. cmdId={}", cmdId);
            return null;
        }
        long startTime = System.currentTimeMillis();

        GeneratedMessageV3 protoMsg = (GeneratedMessageV3) protoWorker.getProtobufDecode().invoke(null, data);
        Pack pack = (Pack) protoWorker.getMethod().invoke(protoWorker.getHandler(), obj, protoMsg);

        long usedTime = System.currentTimeMillis() - startTime;

        if (usedTime > 1000L) { // 协议处理太久
            log.warn("proto worker slowly. cmdId = {}, used = {}", cmdId,usedTime);
        }

        return pack;
    }
    public Pack dispatch(byte[] data, Object obj) throws InvocationTargetException, IllegalAccessException {
        Pack decode = PackCodec.decode(data);
        return dispatch(decode.getCmdId(), decode.getData(), obj);
    }
    public Pack dispatch(Pack pack, Object obj) throws InvocationTargetException, IllegalAccessException {
        return dispatch(pack.getCmdId(), pack.getData(), obj);
    }

    /**
     * 判断cmdId是否有相关的处理方法
     */
    public boolean hasWorker(int cmdId) {
        return workerMap.containsKey(cmdId);
    }

}
